package func_controller;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ResourceBundle;

import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;

import org.json.JSONObject;

import baidu_ai_sr.BaiduSpeechRecognize;
import cn.rmshadows.joffice.JofficeToolKit;
import cn.rmshadows.joffice.PrimaryController;
import cn.rmshadows.joffice.RecordingUtil;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TextArea;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.AnchorPane;
import joffice_vad.Jvad;

public class BaiduSpeechRecognizeController implements Initializable {
	// 检查conf文件夹配置
	public static File bdConf = new File("conf/01-baidu_ai.conf");;
	// Mixer名称
	static LinkedList<String> mixer_name = new LinkedList<>();
	// Mixer列表
	static LinkedList<Mixer.Info> mixer_info = new LinkedList<>();
	// dev_pid 识别模型的数组
	static final String[] BD_DEV_PID = new String[] { "1537:普通话(纯中文识别)", "1737:英语", "1637:粤语", "1837:四川话",
			"1936:普通话远场" };
	// 模型名称
	static LinkedList<String> m_name = new LinkedList<>();
	// 模型路径
	static LinkedList<String> m_dpid = new LinkedList<>();
	// 拖拽文件功能
	// 拖拽文件是否成功
	static boolean is_drag_success = false;
	// vosk识别结果文本框的文字
	static String bd_text = null;
	// 拖拽文件列表
	List<File> drag_files = null;
	// 是否正在识别
	public static boolean is_bding = false;
	// 是否正在录音，用于麦克风识别
	public static boolean is_recording = false;
	// 静态按钮
	static Button s_bd_rec_mic;
	static Button s_bd_rec_file;

	// 注意：TextArea输入中文需要-Djdk.gtk.version=2参数
	@FXML
	AnchorPane cur_ap;
	@FXML
	SplitPane fv_sp;
	@FXML
	AnchorPane fv_ap;
	@FXML
	TextArea bd_ta;
	@FXML
	AnchorPane fo_ap;
	// 识别麦克风
	@FXML
	Button bd_rec_mic;
	// 识别文件
	@FXML
	Button bd_rec_file;
	// 一键复制
	@FXML
	Button bd_copy;
	// 一键清除
	@FXML
	Button bd_clear;
	// 帮助
	@FXML
	Button bd_help;
	// 识别模型选择
	@FXML
	ComboBox<String> bd_model;
	// 选择麦克风
	@FXML
	ComboBox<String> bd_mic;
	// 字体大小
	static int font_size;
	static String font_css = "-fx-font-size: ";
	public static TextArea s_bd_ta = null;

	@Override
	public void initialize(URL location, ResourceBundle resources) {
		// 清空
		mixer_name.clear();
		mixer_info.clear();
		m_name.clear();
		m_dpid.clear();
		if (!bdConf.exists()) {
			try {
				bdConf.createNewFile();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}
		// 设置按钮
		s_bd_rec_mic = bd_rec_mic;
		s_bd_rec_file = bd_rec_file;
		// 读取Mixers
		mixer_info = JofficeToolKit.displayMixerInfo();
		for (Mixer.Info i : mixer_info) {
			mixer_name.add(i.getName());
		}
		ObservableList<String> mixer_list = FXCollections.observableList(mixer_name);
		bd_mic.setItems(mixer_list);
		int default_mixer = mixer_info.indexOf(AudioSystem.getMixer(null).getMixerInfo());
		bd_mic.getSelectionModel().select(default_mixer);
		// 识别模型
		for (String m : BD_DEV_PID) {
			String[] temp_m = m.split(":");
			m_dpid.add(temp_m[0]);
			m_name.add(temp_m[1]);
		}
		ObservableList<String> model_list = FXCollections.observableList(m_name);
		bd_model.setItems(model_list);
		bd_model.getSelectionModel().selectFirst();

		// 设置文本框字体
		s_bd_ta = bd_ta;
		bd_ta.setWrapText(true);
		font_size = 18;
		String tfs = font_css + font_size;
		bd_ta.setStyle(tfs);
		tfs = null;
		KeyCombination inc = new KeyCodeCombination(KeyCode.EQUALS, KeyCombination.CONTROL_DOWN);
		KeyCombination dec = new KeyCodeCombination(KeyCode.MINUS, KeyCombination.CONTROL_DOWN);
		// 字体增大、缩小
		bd_ta.setOnKeyPressed(e -> {
			if (inc.match(e)) {
				font_size += 1;
				String fs = font_css + font_size;
				bd_ta.setStyle(fs);
			} else if (dec.match(e)) {
				if (font_size != 1) {
					font_size -= 1;
					String fs = font_css + font_size;
					bd_ta.setStyle(fs);
				}
			}
		});
		PrimaryController.fv_change = false;
	}

	// 识别麦克风
	@FXML
	void bd_rec_mic(MouseEvent event) {
		if (!is_bding) {
			is_bding = true;
			// 获取选择的模型
			int selected_model = bd_model.getSelectionModel().getSelectedIndex();
			String dev_pid = m_dpid.get(selected_model);
			HashMap<String, Object> opt = new LinkedHashMap<String, Object>();
			opt.put("dev_pid", Integer.valueOf(dev_pid));
			// 获取选择的设备
			int selected = bd_mic.getSelectionModel().getSelectedIndex();
			System.out.println("麦克风选择："+String.valueOf(selected));
			Mixer.Info selected_mixer = mixer_info.get(selected);
//			System.out.println("麦克风选择："+String.valueOf(selected));
			// 录音时长
			final int RECORD_TIME;
			if (event.getButton() == MouseButton.SECONDARY) {
				RECORD_TIME = 3600000;// 一小时
			} else {
				RECORD_TIME = 58000; // 58 seconds
			}
			File wavFile = new File("bd_record_mic.wav");
			if (wavFile.exists()) {
				wavFile.delete();
			}
			wavFile.deleteOnExit();
			final RecordingUtil recorder = new RecordingUtil(JofficeToolKit.reqFormat(), selected_mixer);
			// create a separate thread for recording
			new Thread(new Runnable() {
				@Override
				public void run() {
					// 开始录音
					new Thread(new Runnable() {
						@Override
						public void run() {
							try {
								System.out.println("Start recording...");
								is_recording = true;
								bd_bt_status(0, true);
								recorder.start();
							} catch (LineUnavailableException ex) {
								ex.printStackTrace();
								try {
									recorder.stop();
								} catch (IOException e) {
									e.printStackTrace();
								}
								is_recording = false;
								bd_bt_status(0, false);
								return;
							} catch (IllegalArgumentException e) {
								e.printStackTrace();
								Platform.runLater(new Runnable() {
									@Override
									public void run() {
										Alert alert = new Alert(AlertType.ERROR);
										alert.setTitle("错误");
										alert.setHeaderText(null);
										alert.setContentText("录制音频出错 ！");
										alert.showAndWait();
									}
								});
								bd_bt_status(0, false);
								return;
							}
						}
					}).start();
					// i = 60000/100 600 60.0
					for (int i = RECORD_TIME/100; i > 0; i--) {
						// 停止
						if(!is_bding || PrimaryController.fv_index != 1) {//PrimaryController.fv_change
							bdSetText("", false);
							try {
								recorder.stop();
							} catch (IOException e) {
								e.printStackTrace();
							}
							is_recording = false;
							System.out.println("录音中断。");
							return;
						}
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						bdSetText(String.format("还有 %d 秒。", i/10-1), false);
						if (!is_recording || i == 1) {
							try {
								bdSetText("", false);
								recorder.stop();
								is_recording = false;
								recorder.save(wavFile);
								bd_bt_status(0, true);
								// 开始识别
								File f = new File(BaiduSpeechRecognize.convert2bdPCM(wavFile.getAbsolutePath()));
								// 如果文件读取出错
								if (!f.exists()) {
									bd_bt_status(0, false);
									bd_ta.setText("录音文件保存出错。");
									return;
								}
								// 百度语音识别文件输入列表
								LinkedList<File> input_files = new LinkedList<File>();
								// 如果PCM文件大于一分钟 文件大小大约是1920k
								if (f.length() >= 1919900) {
									// 开始Jvad
									Jvad.Jvad_File(f.getAbsolutePath());
									for (File j : JofficeToolKit.listJvadFile()) {
										System.out.println(j.getName());
										input_files.add(j);
									}
								} else {
									input_files.add(f);
								}
								// 获取选择的模型
								int selected_model = bd_model.getSelectionModel().getSelectedIndex();
								String dev_pid = m_dpid.get(selected_model);
								HashMap<String, Object> opt = new LinkedHashMap<String, Object>();
								opt.put("dev_pid", Integer.valueOf(dev_pid));
								List<String> bd_re = BaiduSpeechRecognize.baidu_asr_files(input_files, opt, true, true,
										0);
								bd_ta.clear();
								for (String j : bd_re) {
									bd_ta.appendText(j);
								}
								return;
							} catch (IOException e) {
								e.printStackTrace();
							}
						}
					}
					try {
						System.out.println("强制保存");
						recorder.stop();
						is_recording = false;
						recorder.save(wavFile);
					} catch (IOException e) {
						e.printStackTrace();
					}
					bd_bt_status(0, true);
					File f = new File(BaiduSpeechRecognize.convert2bdPCM(wavFile.getAbsolutePath()));
					// 如果文件读取出错
					if (!f.exists()) {
						bd_bt_status(0, false);
						bd_ta.setText("文件读取出错。");
						return;
					}
					// 百度语音识别文件输入列表
					LinkedList<File> input_files = new LinkedList<File>();
					// 如果PCM文件大于一分钟 文件大小大约是1920k
					if (f.length() >= 1919900) {
						// 开始Jvad
						Jvad.Jvad_File(f.getAbsolutePath());
						for (File j : JofficeToolKit.listJvadFile()) {
							System.out.println(j.getName());
							input_files.add(j);
						}
					} else {
						input_files.add(f);
					}
					// 获取选择的模型
					int selected_model = bd_model.getSelectionModel().getSelectedIndex();
					String dev_pid = m_dpid.get(selected_model);
					HashMap<String, Object> opt = new LinkedHashMap<String, Object>();
					opt.put("dev_pid", Integer.valueOf(dev_pid));
					List<String> bd_re = BaiduSpeechRecognize.baidu_asr_files(input_files, opt, true, true, 0);
					bd_ta.clear();
					for (String j : bd_re) {
						bd_ta.appendText(j);
					}
				}
			}).start();
		} else {
			// 如果正在识别、打断
			if (is_recording) {
				is_recording = false;
			} else {
				bd_bt_status(0, false);
			}
		}
	}

	@FXML
	void bd_rec_file() {
		if (!is_bding) {
			bd_bt_status(1, true);
			// 获取文件路径
			File f = new File(
					BaiduSpeechRecognize.convert2bdPCM(bd_ta.getText().replaceAll("\n", "").replaceAll("\r\n", "")));
			// 如果文件读取出错
			if (!f.exists()) {
				bd_bt_status(1, false);
				bd_ta.setText("文件读取出错。");
				return;
			}
			// 百度语音识别文件输入列表
			LinkedList<File> input_files = new LinkedList<File>();
			// 如果PCM文件大于一分钟 文件大小大约是1920k
			if (f.length() >= 1919900) {
				// 开始Jvad
				Jvad.Jvad_File(f.getAbsolutePath());
				for (File i : JofficeToolKit.listJvadFile()) {
					System.out.println(i.getName());
					input_files.add(i);
				}
			} else {
				input_files.add(f);
			}
			// 获取选择的模型
			int selected_model = bd_model.getSelectionModel().getSelectedIndex();
			String dev_pid = m_dpid.get(selected_model);
			HashMap<String, Object> opt = new LinkedHashMap<String, Object>();
			opt.put("dev_pid", Integer.valueOf(dev_pid));
			// 启用一个新得线程识别
			Thread bd_file_asr_thread = new Thread(new Runnable() {
				@Override
				public void run() {
					List<String> bd_re = BaiduSpeechRecognize.baidu_asr_files(input_files, opt, true, true, 1);
					bd_ta.clear();
					for (String i : bd_re) {
						bd_ta.appendText(i);
					}
				}
			});
			bd_file_asr_thread.start();
		} else {
			// 如果正在识别、打断
			bd_bt_status(1, false);
		}

	}

	@FXML
	void bd_copy() {
		bd_ta.selectAll();
		bd_ta.copy();
	}

	@FXML
	void bd_clear() {
		bd_ta.clear();
	}

	@FXML
	void bd_help(MouseEvent e) {
		if (e.getButton().equals(MouseButton.PRIMARY)) {
			bd_ta.setText("概述：百度语音识别\n功能按键：\n麦克风识别： 主键单次识别不超过60秒，副键识别不超过一小时。\n" + "识别文件：可拖拽识别。\n"
					+ "一键复制：一键复制文本。\n一键清除：清空文本框。\n帮助/属性：主键显示此文本，副键显示音频属性。");
		} else if (e.getButton().equals(MouseButton.SECONDARY)) {
			// 显示音频信息
			/*
			 * { "streams": [ { "index": 0, "codec_name": "pcm_s16le", "codec_long_name":
			 * "PCM signed 16-bit little-endian", "codec_type": "audio", "codec_time_base":
			 * "1/16000", "codec_tag_string": "[1][0][0][0]", "codec_tag": "0x0001",
			 * "sample_fmt": "s16", "sample_rate": "16000", "channels": 1,
			 * "bits_per_sample": 16, "r_frame_rate": "0/0", "avg_frame_rate": "0/0",
			 * "time_base": "1/16000", "duration_ts": 11468800, "duration": "716.800000",
			 * "bit_rate": "256000", "disposition": { "default": 0, "dub": 0, "original": 0,
			 * "comment": 0, "lyrics": 0, "karaoke": 0, "forced": 0, "hearing_impaired": 0,
			 * "visual_impaired": 0, "clean_effects": 0, "attached_pic": 0,
			 * "timed_thumbnails": 0 } } ] }
			 */
			JSONObject ap = JofficeToolKit.showAudioInfo(bd_ta.getText().replaceAll("\n", "").replaceAll("\r\n", ""));
//			bd_ta.setText(ap.toString(2));
			try {
				if (ap.toString(2).equals("{}")) {
					// 如果获取到空白信息
					Platform.runLater(new Runnable() {
						@Override
						public void run() {
							Alert alert = new Alert(AlertType.ERROR);
							alert.setTitle("错误");
							alert.setHeaderText(null);
							alert.setContentText("获取信息为空，请检查 ！");
							alert.showAndWait();
						}
					});
				} else {// 其他情况
					String streams = ap.get("streams").toString().substring(1,
							ap.get("streams").toString().length() - 1);
					JSONObject sap = new JSONObject(streams);
					String duration = sap.getString("duration");// 时长
					String bit_rate = sap.getString("bit_rate");// 比特率
					String codec_tag_string = sap.getString("codec_tag_string");// 标记
					String codec_long_name = sap.getString("codec_long_name");// 格式全称
					String sample_rate = sap.getString("sample_rate");// 采样率
					int channels = sap.getInt("channels");// 声道数
					String codec_name = sap.getString("codec_name");// ffmpeg格式代号
					String zh_info = String.format(
							"时长: %s\n采样率: %s\n声道数: %s\n格式全称: %s\nFFMPEG格式代号: %s\n比特率: %s\n标签: %s", duration,
							sample_rate, String.valueOf(channels), codec_long_name, codec_name, bit_rate,
							codec_tag_string);
					bd_ta.appendText(String.format("\n简要信息：%s\n详细信息：\n%s", zh_info, ap.toString(2)));
				}
			} catch (Exception e2) {
				e2.printStackTrace();
			}
		} else {

		}
	}

	// 拖拽功能
	@FXML
	void bdOnDragEntered(DragEvent event) {
		bd_text = bd_ta.getText();
		// 如果正在识别，则无法拖拽释放
		if (is_bding) {
			bd_ta.setText("请先停止语音识别后再进行文件拖拽！");
			is_drag_success = false;
		} else {
			bd_ta.setText("拖拽文件到这里进行识别。");
		}
		event.consume();
	}

	@FXML
	void bdOnDragOver(DragEvent event) {
		if (is_bding) {
			is_drag_success = false;
		} else {
			// 没有这句话就没法DragDropped
			event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
		}
		event.consume();
	}

	@FXML
	void bdOnDragDropped(DragEvent event) {
		Dragboard db = event.getDragboard();
		if (db.hasFiles()) {
			System.out.println(db.getFiles().toString());
			drag_files = db.getFiles();
			is_drag_success = true;
		} else {
			drag_files = null;
			is_drag_success = false;
		}
		/*
		 * let the source know whether the string was successfully transferred and used
		 */
		event.setDropCompleted(is_drag_success);
		event.consume();
	}

	@FXML
	void bdOnDragExited(DragEvent event) throws InterruptedException {
		if (is_drag_success) {
			bd_bt_status(1, true);
			// 获取选择的模型
			int selected_model = bd_model.getSelectionModel().getSelectedIndex();
			String dev_pid = m_dpid.get(selected_model);
			HashMap<String, Object> opt = new LinkedHashMap<String, Object>();
			opt.put("dev_pid", Integer.valueOf(dev_pid));
			bd_ta.clear();
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						BaiduSpeechRecognize.baidu_drag_asr(drag_files, opt);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}).start();
		} else {
			// 失败
			if (!is_bding) {
				bd_ta.setText("获取文件失败。");
			}
			// 还原
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					bd_ta.setText(bd_text);
				}
			}).start();
		}
		event.consume();
	}

	/**
	 * 显示识别进度的方法
	 * 
	 * @param a 已完成数量
	 * @param b 总量
	 */
	public static void setRate(int a, int b) {
		Platform.runLater(new Runnable() {
			@Override
			public void run() {
				s_bd_ta.setText(String.format("%d/%d", a, b));
			}
		});
	}

	/**
	 * 供外部调用的更改文本框内容的方法
	 * 
	 * @param s        字符串
	 * @param isAppend 是否添加在末尾
	 */
	public static void bdSetText(String s, boolean isAppend) {
		if (isAppend) {
			Platform.runLater(new Runnable() {
				@Override
				public void run() {
					s_bd_ta.appendText(s);
				}
			});
		} else {
			Platform.runLater(new Runnable() {
				@Override
				public void run() {
					s_bd_ta.setText(s);
				}
			});
		}
	}

	/**
	 * 外部调整识别按钮状态的方法
	 * 其中，对于按钮0有三个状态：
	 * true时：
	 * 如果正在录音->正在录音
	 * 如果不在录音->正在识别
	 * false时：
	 * 如果正在录音->语音识别(60s)
	 * 如果不在录音->语音识别(60s),带有不正常报错
	 * 
	 * @param type   0:bdrecmic按钮 1:bdrecfile按钮
	 * @param status true正在识别 false识别结束
	 */
	public static void bd_bt_status(int type, boolean status) {
		if (type == 0) {
			if (status) {
				is_bding = true;
				Platform.runLater(new Runnable() {
					@Override
					public void run() {
						if (is_recording) {
							s_bd_rec_mic.setText("正在录音");
						} else {
							s_bd_rec_mic.setText("正在识别");
						}
					}
				});
			} else {
				is_bding = false;
				Platform.runLater(new Runnable() {
					@Override
					public void run() {
						if (!is_recording) {
							s_bd_rec_mic.setText("语音识别(60s)");
						} else {
							s_bd_rec_mic.setText("语音识别(60s)");
							System.out.println("ERROR:bd_bt_status出错。");
						}
					}
				});
			}
		} else if (type == 1) {
			if (status) {
				is_bding = true;
				Platform.runLater(new Runnable() {
					@Override
					public void run() {
						s_bd_rec_file.setText("正在识别");
					}
				});
			} else

			{
				is_bding = false;
				Platform.runLater(new Runnable() {
					@Override
					public void run() {
						s_bd_rec_file.setText("文件识别");
					}
				});
			}
		}
	}

}